﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using TerrainGenerator.Geometry;

namespace TerrainGenerator
{
    class BinaryTriangleTree
    {
        int width;
        int height;

        VarianceTree varianceTree;

        public BinaryTriangle LeftRoot { get { return leftRoot; } }
        public BinaryTriangle RightRoot { get { return rightRoot; } }

        BinaryTriangle leftRoot;
        BinaryTriangle rightRoot;

        int nVertices;
        VertexBuffer vertexBuffer;
        int nIndices;
        IndexBuffer indexBuffer;

        float offsetX;
        float offsetY;

        Vector3[,] normalmap;

        public BinaryTriangleTree(float[,] heightmap, Vector3[,] normalmap): this(heightmap, normalmap, 0.0f, 0.0f) { }

        public BinaryTriangleTree(float[,] heightmap, Vector3[,] normalmap, float offsetX, float offsetY)
        {
            width = heightmap.GetLength(0);
            height = heightmap.GetLength(1);
            varianceTree = new VarianceTree(heightmap);

            leftRoot = rightRoot = null;
            this.offsetX = offsetX;
            this.offsetY = offsetY;

            this.normalmap = normalmap;
        }

        public void GenerateRoot()
        {
            leftRoot = new BinaryTriangle(varianceTree.Triangles[1]);
            rightRoot = new BinaryTriangle(varianceTree.Triangles[2]);

            leftRoot.BaseNeighbor = rightRoot;
            rightRoot.BaseNeighbor = leftRoot;

            leftRoot.Tree = varianceTree;
            rightRoot.Tree = varianceTree;
        }

        public void Generate(float maxVariance, Vector3 cameraPosition)
        {
            if (leftRoot == null || rightRoot == null)
            {
                GenerateRoot();
            }

            RecursiveSplit(leftRoot, maxVariance, cameraPosition);
            RecursiveSplit(rightRoot, maxVariance, cameraPosition);
        }

        public void GenerateBuffers(GraphicsDevice graphicsDevice)
        {
            VertexPositionNormalColored[] vertices = Vertices();
            int[] indices = Indices();

            for (int x = 0; x < width; ++x)
            {
                for (int y = 0; y < height; ++y)
                {
                    vertices[Coordinate2Index(x, y)].Normal = normalmap[x, y];
                }
            }

            //// calculate vertex normals
            //for (int i = 0; i < indices.Length; i += 3)
            //{
            //    Vector3 vec1 = vertices[indices[i + 1]].Position - vertices[indices[i]].Position;
            //    Vector3 vec2 = vertices[indices[i]].Position - vertices[indices[i + 2]].Position;
            //    Vector3 normal = Vector3.Cross(vec1, vec2);
            //    normal.Normalize();
            //    //float area = 0.5f * normal.Length();
            //    //normal *= area;
            //    vertices[indices[i]].Normal += normal;
            //    vertices[indices[i + 1]].Normal += normal;
            //    vertices[indices[i + 2]].Normal += normal;
            //}

            //foreach (VertexPositionNormalColored vertex in vertices)
            //{
            //    vertex.Normal.Normalize();
            //}

            nVertices = vertices.Length;
            vertexBuffer = new VertexBuffer(graphicsDevice, typeof(VertexPositionNormalColored), nVertices, BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);

            nIndices = indices.Length;
            indexBuffer = new IndexBuffer(graphicsDevice, typeof(int), nIndices, BufferUsage.WriteOnly);
            indexBuffer.SetData(indices);
        }

        public int CountTriangles()
        {
            if (leftRoot == null)
            {
                return 0;
            }
            else
            {
                return leftRoot.CountTriangles() + rightRoot.CountTriangles();
            }
        }

        void RecursiveSplit(BinaryTriangle triangle, float maxVariance, Vector3 cameraPosition)
        {
            float distance = 1.0f; // TODO: calc real distance
            float variance = triangle.Variance() / distance;

            if (!triangle.IsLeaf() && variance > maxVariance)
            {
                Split(triangle);
                RecursiveSplit(triangle.LeftChild, maxVariance, cameraPosition);
                RecursiveSplit(triangle.RightChild, maxVariance, cameraPosition);
            }
        }

        void Split(BinaryTriangle triangle)
        {
            if (triangle.IsSplit())
            {
                return;
            }

            if (triangle.BaseNeighbor == null)
            {
                // at edge: just split triangle
                RealSplit(triangle);
            }
            else
            {
                if (triangle.BaseNeighbor.BaseNeighbor != triangle)
                {
                    // not a diamond: force split base neighbor
                    Split(triangle.BaseNeighbor);
                }

                // split diamond
                RealSplit(triangle);
                RealSplit(triangle.BaseNeighbor);

                triangle.LeftChild.RightNeighbor = triangle.BaseNeighbor.RightChild;
                triangle.RightChild.LeftNeighbor = triangle.BaseNeighbor.LeftChild;

                triangle.BaseNeighbor.LeftChild.RightNeighbor = triangle.RightChild;
                triangle.BaseNeighbor.RightChild.LeftNeighbor = triangle.LeftChild;
            }

        }

        void RealSplit(BinaryTriangle triangle)
        {
            BinaryTriangle lc = triangle.GenLeftChild();
            BinaryTriangle rc = triangle.GenRightChild();

            triangle.LeftChild = lc;
            triangle.RightChild = rc;

            lc.LeftNeighbor = rc;
            rc.RightNeighbor = lc;

            lc.BaseNeighbor = triangle.LeftNeighbor;
            if (triangle.LeftNeighbor != null)
            {
                if (triangle.LeftNeighbor.BaseNeighbor == triangle)
                {
                    triangle.LeftNeighbor.BaseNeighbor = lc;
                }
                else if (triangle.LeftNeighbor.LeftNeighbor == triangle)
                {
                    triangle.LeftNeighbor.LeftNeighbor = lc;
                }
                else if (triangle.LeftNeighbor.RightNeighbor == triangle)
                {
                    triangle.LeftNeighbor.RightNeighbor = lc;
                }
                else
                {
                    // error
                }
            }

            rc.BaseNeighbor = triangle.RightNeighbor;
            if (triangle.RightNeighbor != null)
            {
                if (triangle.RightNeighbor.BaseNeighbor == triangle)
                {
                    triangle.RightNeighbor.BaseNeighbor = rc;
                }
                else if (triangle.RightNeighbor.LeftNeighbor == triangle)
                {
                    triangle.RightNeighbor.LeftNeighbor = rc;
                }
                else if (triangle.RightNeighbor.RightNeighbor == triangle)
                {
                    triangle.RightNeighbor.RightNeighbor = rc;
                }
                else
                {
                    // error
                }
            }
        }

        public void Draw(GraphicsDevice graphicsDevice, Matrix world, Matrix view, Matrix projection)
        {
            BasicEffect basicEffect = new BasicEffect(graphicsDevice, null);

            basicEffect.Begin();
            basicEffect.World = world;
            basicEffect.View = view;
            basicEffect.Projection = projection;
            basicEffect.VertexColorEnabled = true;
            basicEffect.EnableDefaultLighting();

            graphicsDevice.VertexDeclaration = new VertexDeclaration(graphicsDevice, VertexPositionNormalColored.VertexElements);

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalColored.SizeInBytes);
                graphicsDevice.Indices = indexBuffer;
                graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nVertices, 0, nIndices);
                pass.End();
            }

            basicEffect.End();
        }

        public void Draw(GraphicsDevice graphicsDevice, Matrix world, Matrix view, Matrix projection, Vector3 viewVector, Effect effect)
        {
            Matrix worldInverseTransposeMatrix = Matrix.Transpose(Matrix.Invert(world));

            effect.Begin();
            effect.Parameters["World"].SetValue(world);
            effect.Parameters["View"].SetValue(view);
            effect.Parameters["Projection"].SetValue(projection);
            effect.Parameters["ViewVector"].SetValue(viewVector);
            effect.Parameters["WorldInverseTranspose"].SetValue(worldInverseTransposeMatrix);

            graphicsDevice.VertexDeclaration = new VertexDeclaration(graphicsDevice, VertexPositionNormalColored.VertexElements);

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalColored.SizeInBytes);
                graphicsDevice.Indices = indexBuffer;
                graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nVertices, 0, nIndices);
                pass.End();
            }

            effect.End();
        }

        public VertexPositionNormalColored[] Vertices()
        {
            VertexPositionNormalColored[] vertices = new VertexPositionNormalColored[width * height];
            for (int x = 0; x < width; ++x)
            {
                for (int y = 0; y < height; ++y)
                {
                    vertices[Coordinate2Index(x, y)] = new VertexPositionNormalColored(new Vector3(x+offsetX, varianceTree.Heightmap[x, y], y+offsetY), Vector3.Zero, Color.Green);
                }
            }

            return vertices;
        }

        public int[] Indices()
        {
            if (leftRoot != null)
            {
                List<int> leftIndices = GetIndices(leftRoot);
                List<int> rightIndices = GetIndices(rightRoot);
                return leftIndices.Concat(rightIndices).ToArray();
            }
            else
            {
                return new int[0];
            }
        }

        List<int> GetIndices(BinaryTriangle triangle)
        {
            List<int> indices = new List<int>();
            if (triangle.IsSplit())
            {
                indices.AddRange(GetIndices(triangle.LeftChild));
                indices.AddRange(GetIndices(triangle.RightChild));
            }
            else
            {
                indices.Add(Coordinate2Index(triangle.Triangle.X1, triangle.Triangle.Y1));
                indices.Add(Coordinate2Index(triangle.Triangle.X2, triangle.Triangle.Y2));
                indices.Add(Coordinate2Index(triangle.Triangle.X3, triangle.Triangle.Y3));
            }

            return indices;
        }

        int Coordinate2Index(int x, int y)
        {
            return y * width + x;
        }
    }
}
